/*
 *  de.karnik.helper - Library for basic helper functions
 *  Copyright (C) 2007 Markus Karnik
 *
 *  This program is free software; you can redistribute it and/or modify it 
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3 of the License, 
 *  or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along 
 *  with this program; if not, see <http://www.gnu.org/licenses/>.
 *  
 */
package de.karnik.helper;

import java.util.Random;

/**
 * Useful methods to handle, convert or create arrays.
 * 
 * @author Markus Karnik - markus.karnik@my-design.org
 * @version 1.0
 * @since 1.0
 */
public class ArrayFunctions {

	/**
	 * Compares two byte arrays.
	 * 
	 * @param array1 The first array to compare.
	 * @param array2 The second array to compare.
	 * @return <b>true</b> if equal, <b>false</b> otherwise.
	 */
	public static boolean compare( byte[] array1, byte[] array2 ) {
		
		if( array1.length != array2.length )
			return false;
		
		for( int i = 0; i < array1.length; i++ )
			if( array1[ i ] != array2[ i ] )
				return false;
		
		return true;
	}
	
	/**
	 * Returns a random byte array with the length of byteCount. Can be used to create random keys.
	 * 
	 * @param byteCount The length for the random byte array
	 * @return A random byte array with the length of byteCount.
	 */
	public static byte[] generateRandomByteArray( int byteCount ) {
		byte[] tempArray = new byte[ byteCount ];
		new Random().nextBytes( tempArray );
		
		return tempArray;
	}
	
	/**
	 * Creates and returns a byte array with the contents of array1 and array2-
	 * 
	 * @param array1 First array to merge.
	 * @param array2 Second array to merge.
	 * @return A byte array with the contents of array1 and array2 or <b>null</b> if arraylength would be 0.
	 */
	public static byte[] merge( byte[] array1, byte[] array2 ) {
	
		int length1 = 0;
		int length2 = 0;
		
		if( array1 != null )
			length1 = array1.length;

		if( array2 != null )
			length2 = array2.length;
		
		if( ( length1 + length2 ) == 0 )
			return null;
		
		byte[] newArray = new byte[ length1 + length2 ];
		
		for( int i = 0; i < newArray.length; i++ ) {
			if( i < length1 )
				newArray[ i ] = array1[ i ];
			else
				newArray[ i ] = array2[ i - length1 ];
		}
		
		return newArray;
	}
	
	/**
	 * Returns the big endian encoded array representing the integer value. The length of the array is length.
	 *
	 * @param value Value for the array.
	 * @param length Length for the array ( max 4 ).
	 * @return The big endian encoded array representing the integer value.
	 */
	public static byte[] intToByteArray( final int length, final int value ) {

		byte[] retVal = new byte[ length ];
		
		for( int i = 0; i < length; i++ )
			retVal[ i ] = ( byte ) ( ( value >> ( ( length - ( i + 1 ) ) * 8 ) ) & 0x000000FF );
		
		return retVal;
	}
	
	/**
	 * Returns the big endian encoded array representing the long value. The length of the array is length.
	 *
	 * @param value Value for the array.
	 * @param length Length for the array ( max 8 ).
	 * @return The big endian encoded array representing the long value.
	 */
	public static byte[] longToByteArray( final int length, final long value ) {

		byte[] retVal = new byte[ length ];
		
		for( int i = 0; i < length; i++ )
			retVal[ i ] = ( byte ) ( ( value >> ( ( length - ( i + 1 ) ) * 8 ) ) & 0x00000000000000FF );
		
		return retVal;
	}
	
	/**
	 * Adds a leading big endian length header to the data byte[].
	 * 
	 * @param data The data without length information.
	 * @return The data with length information.
	 */
	public static byte[] addBigEndianLengthHeader( int headerlength, byte[] data ) {
		
		byte[] dataLength;
		byte[] retVal;
		
		if( data != null && data.length > 0 ) {
			dataLength = intToByteArray( headerlength, data.length );
			retVal = new byte[ data.length + dataLength.length ];
			
			for( int i = 0; i < dataLength.length; i++ )
				retVal[ i ] = dataLength[ i ];
			
			for( int i = dataLength.length; i < retVal.length; i++ )
				retVal[ i ] = data[ i - dataLength.length ];
			
		} else {
			dataLength = intToByteArray( headerlength, 0 );
			retVal = new byte[ dataLength.length ];
			
			for( int i = 0; i < dataLength.length; i++ )
				retVal[ i ] = dataLength[ i ];
		}
		
		return retVal;
	}
	
	/**
	 * Returns the value of a big endian encoded part of a byte array. Max 8 byte!
	 * 
	 * @param offset The offset for the extraction.
	 * @param lengthBytes The length of the extracted data.
	 * @param data The data to extract the big endian value from.
	 * @return The big endian value of the part. 
	 */
	public static long getBigEndianValue( int offset, int lengthBytes, byte[] data ) {
		long value = 0;
		long tempValue = 0;
		for( int i = 0; i < lengthBytes; i++ ) {
			tempValue = ( ( ( int )data[ offset + i ] ) & 0xff ) << ( 8 * ( lengthBytes - ( 1 + i ) ) ); 
			value = value | tempValue;
			tempValue = 0;
		}
		
		return value;
	}
	
	/**
	 * Seperates the next data package from a byte array.  Max 4 byte headerLength!
	 * 
	 * @param cursor Startposition for the  extraction.
	 * @param headerLength Length of the package length header.
	 * @param data The data byte array to extract the data from.
	 * @return The extracted data.
	 */
	public static byte[] getNextPackage( int cursor, int headerLength, byte[] data ) {
		int dataLength = ( int )ArrayFunctions.getBigEndianValue( cursor, headerLength, data );
		cursor += headerLength;
		
		return ArrayFunctions.seperateDataFromArray( cursor, dataLength,  data );
	}
	
	/**
	 * Seperates and returns a section from a byte[].
	 * 
	 * @param offset Offset of the section to seperate.
	 * @param lengthBytes Length of the section to seperate.
	 * @param data The data to seperator the section from.
	 * @return The seperated section.
	 */
	public static byte[] seperateDataFromArray( int offset, int lengthBytes, byte[] data ) {
		byte[] bytes = new byte[ lengthBytes ];
		
		for( int i = 0; i < lengthBytes; i++ ) {
			bytes[ i ] = data[ offset + i ];
		}
		
		return bytes;
	}
	
	/**
	 * Removes leading zeros from given byte[] array. If no zeros available the array stays untouched.
	 * @param value The array with possbile leading zeros.
	 * @return The arry without leading zeros.
	 */
	public static byte[] serialize( byte[] value ) {
		
		// find leading zero count
		int i = 0;
		while( ( int )value[ i ] == 0 )
			i++;
		
		// remove leading 0's
		byte[] tempByteArray = new byte[ value.length - i ];	
		for( int j = 0; j < tempByteArray.length; j++ )
			tempByteArray[ j ] = value[ j + i ];
			
		return tempByteArray;
	}
	
}
